#include <stdio.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdint.h>
#include <ctype.h>
#include <dirent.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <sys/stat.h>


#include "pcie.h"
#include "func.h"
#include "private_common.h"


#define PCIE_TYPE_DESC_LEN 48
#define PCIE_DESC_LEN 160
#define PCIE_ERR_DESC_LEN 256
struct pcie_data{
	char name[64];
	double value[2];
	char unit[2][8];
	char type[2][8];
	uint8_t width[2];
	uint32_t class;
	uint32_t vendor;
	uint32_t device;
	char desc[PCIE_DESC_LEN];
	char type_desc[PCIE_TYPE_DESC_LEN];
	char err_correctable[PCIE_ERR_DESC_LEN];
	char err_fatal[PCIE_ERR_DESC_LEN];
	char err_nonfatal[PCIE_ERR_DESC_LEN];
};

static struct pcie_data* pdata = NULL;
static int pcie_data_len = 0;

static uint32_t read_file_uint32(char* fn)
{
	char buffer[16] = {0};
	char *pbuffer = buffer;
	char *end;
	uint32_t ret;
	int rval = slurpfile(fn, &pbuffer, 16);
        if(rval == SLURP_FAILURE) {
		return 0;
	}
	ret = strtoul(buffer, &end, 16);
	return ret;
}

static int translation_dev_desc(struct pcie_data* pd, char* buffer, uint8_t update)
{
	const char delim[] = "\n";
        char *token;
        char *p, *q;
	uint8_t class = ((0xFF0000 & pd->class) >> 16);
	uint8_t subclass = ((0xFF00 & pd->class) >> 8);
	char str_class[8];
	char str_subclass[8];
	char str_vendor[8];
	char str_device[8];
	sprintf(str_class, "C %0*x", 2, class);
	sprintf(str_subclass, "\t%0*x", 2, subclass);
	sprintf(str_vendor, "%0*x", 4, pd->vendor);
	sprintf(str_device, "\t%0*x", 4, pd->device);

	//printf("%s;%s;%s;%s;%x;%x;%x\n", str_class, str_subclass, str_vendor, str_device, pd->class, pd->vendor, pd->device);

	int len;
        token = strtok(buffer, delim);
        while (token != NULL) {
                p = token;
                if (startsWith(p, str_vendor)) {
			p = skip_token(p);
			len = strlen(p);
			q = p;
			q = skip_whitespace(q);
			snprintf(pd->desc, PCIE_DESC_LEN-1,"%s", q);
			while ((p = strtok(NULL, delim)) != NULL) {
				if (startsWith(p, str_device)) {
					p = skip_token(p);
					p = skip_whitespace(p);
					snprintf(pd->desc, PCIE_DESC_LEN-1,"%s %s", q, p);
					break;
				} else if (!isspace((unsigned char)*p)) {
					break;
				}
			}
			token = p;
			continue;
		} else if (startsWith(p, str_class)) {
			p = skip_token(p);
			p = skip_token(p);
			q = p;
			q = skip_whitespace(q);
			snprintf(pd->type_desc, PCIE_TYPE_DESC_LEN-1, "%s", q);
			while ((p = strtok(NULL, delim)) != NULL) {
				if (startsWith(p, str_subclass)) {
					p = skip_token(p);
					p = skip_whitespace(p);
					snprintf(pd->type_desc, PCIE_TYPE_DESC_LEN-1, "%s", p);
					break;
				} else if (!isspace((unsigned char)*p)) {
					break;
				}
			}
			token = p;
			continue;
		}
		token = strtok(NULL, delim);
	}
	return 0;
}

static void parse_param(void* ud, void *up, char* st)
{
	struct pcie_data* pd = (struct pcie_data*)ud;
	int* user_p = (int*)up;
	if (ud == NULL || user_p == NULL) return;
	if (*user_p <1 || *user_p >2) return;
	if (st == NULL) return;
	//printf("%s\n", st);
	char* p = st, *p1, *p2;
	double val = strtod(p, &p);
	pd->value[*user_p-1] = val;
	p = skip_whitespace(p);
	p1 = p;
	p = skip_token(p);
	p2 = p;
	p = skip_whitespace(p);
	*p2 = '\0';
	p2 = p;
	p = skip_token(p);
	*p = '\0';
	sprintf(pd->unit[*user_p - 1], "%s", p1);
	sprintf(pd->type[*user_p - 1], "%s", p2);
	//printf("%p,%s:%lf;%s;%s, type=%d\n", pd->name, pd->name, pd->value[*user_p-1] , pd->unit[*user_p - 1], pd->type[*user_p - 1], *user_p);
}

static void parse_width(void* ud, void *up, char* st)
{
	struct pcie_data* pd = (struct pcie_data*)ud;
	int* user_p = (int*)up;
	if (ud == NULL || user_p == NULL) return;
	if (*user_p <1 || *user_p >2) return;
	if (st == NULL) return;
	//printf("%s:%s\n", __func__, st);
	uint8_t w = atoi(st);
	pd->width[*user_p - 1] = w;;
}

static void parse_err(void* ud, void* up, char* st)
{
	struct pcie_data* pd = (struct pcie_data*)ud;
	if (ud == NULL) return;
	if (st == NULL) return;
	int* user_p = (int*)up;
	if (ud == NULL || user_p == NULL) return;
	if (*user_p <1 || *user_p >3) return;
	//printf("%s:%s:%s\n", __func__, pd->name, st);
	int n = strlen(st);
	if (*(st+n-1) == '\n') {
		*(st+n-1) = '\0';
	}
	switch (*user_p) {
		case 1:
			snprintf(pd->err_correctable, PCIE_ERR_DESC_LEN-1, "%s", st);
			break;
		case 2:
			snprintf(pd->err_fatal, PCIE_ERR_DESC_LEN-1, "%s", st);
			break;
		case 3:
			snprintf(pd->err_nonfatal, PCIE_ERR_DESC_LEN-1, "%s", st);
			break;
	}
}

#define SYS_PCI_PATH "/sys/bus/pci/devices/"
static int pcie_init(p_monitor_node *ml, const char* ignore) 
{
	DIR *dir = opendir(SYS_PCI_PATH);
	if (!dir) return -1;
	
	char *pci_ids = NULL, *pci_ids_dup;
	uint32_t file_len = 5 * 1024 * 1024 * sizeof(char);
	uint32_t ret;
	if (pci_ids == NULL) {
		pci_ids = (char*)malloc(file_len);
	}
	int rval = slurpfile("/usr/share/hwdata/pci.ids", &pci_ids, file_len);
	if(rval == SLURP_FAILURE) {
		printf("pci.ids，read error");
		free(pci_ids);
		pci_ids = NULL;
	}
	
	char path[128][8][MAX_LEN_NAME];
	char tmp[128];
	int k,j;
	struct dirent *entry;
	k = 0;
	while ((entry = readdir(dir))) {
		if (entry->d_type != DT_LNK) continue;
		if (ignore && strstr(ignore, entry->d_name)) {
			continue;
		}

		snprintf(path[k][0], MAX_LEN_NAME - 1, "%s%s/current_link_speed", SYS_PCI_PATH, entry->d_name);
		if (exist_file(path[k][0]) != 0) continue;
		snprintf(path[k][1], MAX_LEN_NAME - 1, "%s%s/max_link_speed", SYS_PCI_PATH, entry->d_name);
		if (exist_file(path[k][1]) != 0) continue;	
		
		snprintf(path[k][2], MAX_LEN_NAME - 1, "%s%s/current_link_width", SYS_PCI_PATH, entry->d_name);
		if (exist_file(path[k][2]) != 0) continue;
		snprintf(path[k][3], MAX_LEN_NAME - 1, "%s%s/max_link_width", SYS_PCI_PATH, entry->d_name);
		if (exist_file(path[k][3]) != 0) continue;
		
		snprintf(path[k][4], MAX_LEN_NAME - 1, "%s%s/aer_dev_correctable", SYS_PCI_PATH, entry->d_name);
		if (exist_file(path[k][4]) != 0) continue;

		snprintf(path[k][5], MAX_LEN_NAME - 1, "%s%s/aer_dev_fatal", SYS_PCI_PATH, entry->d_name);
		if (exist_file(path[k][5]) != 0) continue;

		snprintf(path[k][6], MAX_LEN_NAME - 1, "%s%s/aer_dev_nonfatal", SYS_PCI_PATH, entry->d_name);
		if (exist_file(path[k][6]) != 0) continue;

		snprintf(path[k][7], MAX_LEN_NAME - 1, "%s", entry->d_name);
		k++;
		if (k>=128) break;
	}
	closedir(dir);
	int factor = 7;
	size_t  s = sizeof(monitor_node) * (k * factor + 1);
	*ml = (p_monitor_node)malloc(s);
	pdata = (struct pcie_data*)malloc(sizeof(struct pcie_data)*k);
	memset(*ml, 0, s);
	static int type[4] = {1, 2, 1, 2};
	static int err_type[4] = {1, 2, 3};
	pcie_data_len = k;
	for (j = 0;j < k; j++) {
		snprintf((pdata+j)->name, 63, "%s", path[j][7]);
		//snprintf((pdata+j)->name, 63, "%s-%d", "abc", j);
		init_monitor_info_interface(&((*ml+factor*j)->oper), parse_param, (void*)(pdata+j), (void*)(&(type[0])));
		init_monitor_node(*ml+factor*j, path[j][0],   3, 0);
		init_monitor_info_interface(&((*ml+factor*j+1)->oper), parse_param, (void*)(pdata+j), (void*)(&(type[1])));
		init_monitor_node(*ml+factor*j+1, path[j][1], 3, 0);
		
		init_monitor_info_interface(&((*ml+factor*j+2)->oper), parse_width, (void*)(pdata+j), (void*)(&(type[2])));
		init_monitor_node(*ml+factor*j+2, path[j][2], 3, 0);
		init_monitor_info_interface(&((*ml+factor*j+3)->oper), parse_width, (void*)(pdata+j), (void*)(&(type[3])));
		init_monitor_node(*ml+factor*j+3, path[j][3], 3, 0);

		init_monitor_info_interface(&((*ml+factor*j+4)->oper), parse_err, (void*)(pdata+j), (void*)(&(err_type[0])));
		init_monitor_node(*ml+factor*j+4, path[j][4], 3, 0);

		init_monitor_info_interface(&((*ml+factor*j+5)->oper), parse_err, (void*)(pdata+j), (void*)(&(err_type[1])));
		init_monitor_node(*ml+factor*j+5, path[j][5], 3, 0);

		init_monitor_info_interface(&((*ml+factor*j+6)->oper), parse_err, (void*)(pdata+j), (void*)(&(err_type[2])));
		init_monitor_node(*ml+factor*j+6, path[j][6], 3, 0);

		snprintf(tmp, 127, "%s%s/class", SYS_PCI_PATH, path[j][7]);
		(pdata+j)->class = read_file_uint32(tmp);
		snprintf(tmp, 127, "%s%s/vendor", SYS_PCI_PATH, path[j][7]);
		(pdata+j)->vendor = read_file_uint32(tmp);
		snprintf(tmp, 127, "%s%s/device", SYS_PCI_PATH, path[j][7]);
		(pdata+j)->device = read_file_uint32(tmp);
		pci_ids_dup = strdup(pci_ids);
		if (pci_ids_dup) {
			translation_dev_desc((pdata+j), pci_ids_dup, 0);
			free(pci_ids_dup);
		} else {
			printf("strdup failed\n");
		}
	}
	free(pci_ids);
	return k*4;
}

static void pcie_clean()
{
	if (pdata)
		free(pdata);
	pdata = NULL;
	pcie_data_len = 0;
}

static void pcie_update(p_monitor_node *ml)
{
	int num = pcie_data_len*7;
	for (int i = 0;i < num; i++) {
		update_monitor_node(*ml+i);
	}

}

static void  pcie_to_json(cJSON* json) {
	if (pcie_data_len <= 0 || pdata == NULL) return;
	if (json == NULL) return;
	cJSON *node, *item;
	char hexstr_class[16];
	char hexstr_vendor[16];
	char hexstr_device[16];
	//printf("%s:%d\n", __func__, pcie_data_len);
	for (int i = 0;i < pcie_data_len; i++ ) {
                sprintf(hexstr_class, "0x%0*X",  6, (pdata+i)->class);
                sprintf(hexstr_vendor, "0x%0*X", 4, (pdata+i)->vendor);
                sprintf(hexstr_device, "0x%0*X", 4, (pdata+i)->device);
		item = cJSON_GetObjectItemCaseSensitive(json, (pdata+i)->name);
		if (item) {
			node = cJSON_GetObjectItem(json, (pdata+i)->name);
			cJSON_SetNumberValue(cJSON_GetObjectItem(node, "current_link_speed"), (pdata+i)->value[0]);
			cJSON_SetValuestring(cJSON_GetObjectItem(node, "current_unit"), (pdata+i)->unit[0]);
			cJSON_SetNumberValue(cJSON_GetObjectItem(node, "current_width"), (pdata+i)->width[0]);
			cJSON_SetNumberValue(cJSON_GetObjectItem(node, "max_link_speed"), (pdata+i)->value[1]);
			cJSON_SetValuestring(cJSON_GetObjectItem(node, "max_unit"), (pdata+i)->unit[1]);
			cJSON_SetNumberValue(cJSON_GetObjectItem(node, "max_width"), (pdata+i)->width[1]);
			cJSON_SetValuestring(cJSON_GetObjectItem(node, "err_correctable"), (pdata+i)->err_correctable);
			cJSON_SetValuestring(cJSON_GetObjectItem(node, "err_fatal"), (pdata+i)->err_fatal);
			cJSON_SetValuestring(cJSON_GetObjectItem(node, "err_nonfatal"), (pdata+i)->err_nonfatal);
		} else {
			node = cJSON_CreateObject();
			cJSON_AddNumberToObject(node, "current_link_speed", (pdata+i)->value[0]);
			cJSON_AddStringToObject(node, "current_unit", (pdata+i)->unit[0]);
			cJSON_AddNumberToObject(node, "current_width", (pdata+i)->width[0]);
			cJSON_AddNumberToObject(node, "max_link_speed", (pdata+i)->value[1]);
			cJSON_AddStringToObject(node, "max_unit", (pdata+i)->unit[1]);
			cJSON_AddNumberToObject(node, "max_width", (pdata+i)->width[1]);
			cJSON_AddStringToObject(node, "class", hexstr_class);
			cJSON_AddStringToObject(node, "vendor", hexstr_vendor);
			cJSON_AddStringToObject(node, "device", hexstr_device);
			cJSON_AddStringToObject(node, "type_desc", (pdata+i)->type_desc);
			cJSON_AddStringToObject(node, "desc", (pdata+i)->desc);
			cJSON_AddStringToObject(node, "err_correctable", (pdata+i)->err_correctable);
			cJSON_AddStringToObject(node, "err_fatal", (pdata+i)->err_fatal);
			cJSON_AddStringToObject(node, "err_nonfatal", (pdata+i)->err_nonfatal);
			cJSON_AddItemToObject(json, (pdata+i)->name, node);
		}
	}
	return;
}

static uint32_t pcie_to_text(char* buffer, uint32_t size)
{
        static char line[14][PCIE_ERR_DESC_LEN];
        uint32_t k1 = 0, k2 = size;
        int r;

        for (int i = 0;i < pcie_data_len; i++ ) {
                sprintf(line[0],  "%s,current_link_speed,%.2lf", (pdata+i)->name, (pdata+i)->value[0]);
                sprintf(line[1],  "%s,current_unit,%s",          (pdata+i)->name, (pdata+i)->unit[0]);
                sprintf(line[2],  "%s,current_width,%d",         (pdata+i)->name, (pdata+i)->width[0]);
                sprintf(line[3],  "%s,max_link_speed,%.2lf",     (pdata+i)->name, (pdata+i)->value[1]);
                sprintf(line[4],  "%s,max_unit,%s",              (pdata+i)->name, (pdata+i)->unit[1]);
                sprintf(line[5],  "%s,max_width,%d",             (pdata+i)->name, (pdata+i)->width[1]);
                sprintf(line[6],  "%s,class,0x%0*X",             (pdata+i)->name, 6, (pdata+i)->class);
                sprintf(line[7],  "%s,vendor,0x%0*X",            (pdata+i)->name, 4, (pdata+i)->vendor);
                sprintf(line[8],  "%s,device,0x%0*X",            (pdata+i)->name, 4, (pdata+i)->device);
		sprintf(line[9],  "%s,type_desc,%s",             (pdata+i)->name, (pdata+i)->type_desc);
		sprintf(line[10], "%s,desc,%s",                  (pdata+i)->name, (pdata+i)->desc);
		sprintf(line[11], "%s,err_correctable,%s",       (pdata+i)->name, (pdata+i)->err_correctable);
		sprintf(line[12], "%s,err_fatal,%s",             (pdata+i)->name, (pdata+i)->err_fatal);
		sprintf(line[13], "%s,err_nonfatal,%s",          (pdata+i)->name, (pdata+i)->err_nonfatal);
		for (int j = 0; j < 14; j++) {
			r = snprintf(buffer+k1, k2, "%s\n", line[j]);
			if (r <= 0) return k1;
			k1 += r;
			if (k1 >= size) return k1;
			k2 -= r;
		}
        }
        return k1;
}



static void pcie_correction(const char* value)
{
	static char buffer[65536];
	int len = strlen(value);
	if (len > 65535) {
		printf("data too long\b");
		return ;
	}
	memcpy(buffer, value, len);
	char* st = buffer;
	const char delim[] = "\n";
	char* token = strtok(st, delim);
	char *p1, *p2, *p3, *p;
        while (token != NULL) {
		p1 = token;
		p2 = strchr(p1, ',');
		p = p2;
		p2++;
		*p = '\0';
		p3 = strchr(p2, ',');
		p = p3;
		p3++;
		*p = '\0';
		for (int i = 0;i < pcie_data_len; i++) {
			if (strcmp(p1, (pdata+i)->name) == 0) {
				if (strcmp(p2, "desc") == 0) {
					snprintf((pdata+i)->desc, PCIE_DESC_LEN-1, "%s", p3);
				} else if (strcmp(p2, "type_desc") == 0) {
					snprintf((pdata+i)->type_desc, PCIE_TYPE_DESC_LEN-1, "%s", p3);
				}
				break;
			}
		}
		token = strtok(NULL, delim);
        }
}


void pcie_task_init(ptask t, char* _ignore, uint16_t _uf)
{
        t->init    = pcie_init;
        t->update  = pcie_update;
        t->to_json = pcie_to_json;
        t->clean   = pcie_clean;
        t->json    = NULL;
        t->ignore  = _ignore;
        t->uf      = _uf;
        t->cu      = 0;
	t->correction = pcie_correction;
	t->to_text = pcie_to_text;
}


